using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
using Legacy.Core.SaveGameManagement;

namespace SaveGameConverter
{
    internal sealed class Lsg2Xml
    {
        public static void Convert(string sourceFile, string targetFile)
        {
            Lsg2Xml converter = new Lsg2Xml(sourceFile, targetFile);
            converter.Convert();
        }

        private readonly string _sourceFile;
        private readonly string _targetFile;

        private Lsg2Xml(string sourceFile, string targetFile)
        {
            if (sourceFile == null)
                throw new ArgumentNullException("sourceFile");
            if (targetFile == null)
                throw new ArgumentNullException("targetFile");

            _sourceFile = sourceFile;
            _targetFile = targetFile;
        }

        public void Convert()
        {
            SaveGame sg = new SaveGame();
            sg.Read(_sourceFile);

            XmlDocument doc = new XmlDocument();
            {
                XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", null, null);
                doc.AppendChild(dec);
            }
            
            XmlElement root = doc.CreateElement("SavedGame");
            doc.AppendChild(root);

            Serialize(sg, root);
            
            doc.Save(_targetFile);
        }

        private void Serialize(SaveGame sg, XmlElement root)
        {
            var datas = (Dictionary<string, SaveGameData>)sg.GetPrivateFieldValue("m_saveGameData");

            SerializeMeta(root);
            foreach (SaveGameData data in datas.Values)
                Serialize(data, root);
        }

        private void SerializeMeta(XmlElement parent)
        {
            SaveGameMeta meta;

            using (FileStream fileStream = File.OpenRead(_sourceFile))
            using (BinaryReader p_reader = new BinaryReader(fileStream))
                meta = SaveGame.ReadMetaData(p_reader);

            XmlElement node = CreateChildNode(parent, "SaveGameMeta");
            CreateAttribute(node, "Int64_TimeInTicks", meta.Time.Ticks);
            CreateAttribute(node, "Int64_PlayTimeInTicks", meta.PlayTime.Ticks);
            CreateAttribute(node, "Int32_SaveNumber", meta.SaveNumber);
            CreateAttribute(node, "Int32_SaveGameType", (int)meta.Type);
        }

        private void Serialize(SaveGameData data, XmlElement parent)
        {
            XmlElement node = CreateChildNode(parent, "SaveGameData");
            CreateAttribute(node, "id", data.ID);
            CreateAttribute(node, "sealed", data.GetPrivateFieldValue("m_sealed"));

            Serialize(((Dictionary<string, bool>)data.GetPrivateFieldValue("m_bools")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.Boolean, node);
            Serialize(((Dictionary<string, byte>)data.GetPrivateFieldValue("m_bytes")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.Byte, node);
            Serialize(((Dictionary<string, short>)data.GetPrivateFieldValue("m_shorts")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.Int16, node);
            Serialize(((Dictionary<string, ushort>)data.GetPrivateFieldValue("m_ushorts")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.UInt16, node);
            Serialize(((Dictionary<string, int>)data.GetPrivateFieldValue("m_ints")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.Int32, node);
            Serialize(((Dictionary<string, uint>)data.GetPrivateFieldValue("m_uints")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.UInt32, node);
            Serialize(((Dictionary<string, long>)data.GetPrivateFieldValue("m_longs")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.Int64, node);
            Serialize(((Dictionary<string, ulong>)data.GetPrivateFieldValue("m_ulongs")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.UInt64, node);
            Serialize(((Dictionary<string, float>)data.GetPrivateFieldValue("m_floats")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.Single, node);
            Serialize(((Dictionary<string, double>)data.GetPrivateFieldValue("m_doubles")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.Double, node);
            Serialize(((Dictionary<string, string>)data.GetPrivateFieldValue("m_strings")).Select(p => Tuple.Create(p.Key, (object)p.Value)), Helper.String, node);

            var objects = (Dictionary<string, SaveGameData>)data.GetPrivateFieldValue("m_dataObjects");
            if (objects.Count > 0)
            {
                XmlElement childs = CreateChildNode(node, Helper.Object);
                foreach (SaveGameData obj in objects.Values)
                    Serialize(obj, childs);
            }
        }

        private void Serialize(IEnumerable<Tuple<string, object>> pairs, string name, XmlElement parent)
        {
            XmlElement node = null;
            
            IEnumerator<Tuple<string, object>> en = pairs.GetEnumerator();
            while (en.MoveNext())
            {
                if (node == null)
                    node = CreateChildNode(parent, name);

                XmlElement entry = CreateChildNode(node, "Entry");
                CreateAttribute(entry, "name", en.Current.Item1);
                CreateAttribute(entry, "value", en.Current.Item2);
            }
        }

        private static XmlElement CreateChildNode(XmlElement parent, string name)
        {
            XmlElement node = parent.OwnerDocument.CreateElement(name);
            parent.AppendChild(node);
            return node;
        }

        private static XmlAttribute CreateAttribute(XmlElement node, string name, object value)
        {
            XmlAttribute attr = node.OwnerDocument.CreateAttribute(name);
            attr.Value = System.Convert.ToString(value, CultureInfo.InvariantCulture);
            node.Attributes.Append(attr);
            return attr;
        }
    }
}